package com.koomii.common.cached;

import net.spy.memcached.AddrUtil;
import net.spy.memcached.ConnectionFactoryBuilder;
import net.spy.memcached.MemcachedClient;
import net.spy.memcached.auth.AuthDescriptor;
import net.spy.memcached.auth.PlainCallbackHandler;
import net.spy.memcached.transcoders.Transcoder;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;

import java.util.List;
import java.util.concurrent.Future;

/**
 *
 * <p/>
 *
 *
 */
public class CacheUtil implements InitializingBean {
    private final static Log log = LogFactory.getLog(CacheUtil.class);
    private boolean address;
    private MemcachedClient client;
    private List<String> cacheServerAddress;
    private String userName;//ali缓存访问用户名
    private String pwd;//ali缓存访问密码

    public void setCacheServerAddress(List<String> cacheServerAddress) {
        this.cacheServerAddress = cacheServerAddress;
    }

    public <T> Future<Boolean> add(String key, int exp, T o, Transcoder<T> tc) {
        return client.add(key, exp, o, tc);
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    /**
     * Add an object to the cache (using the default transcoder)
     * iff it does not exist already.
     * <p/>
     * <p>
     * The <code>exp</code> value is passed along to memcached exactly as
     * given, and will be processed per the memcached protocol specification:
     * </p>
     * <p/>
     * <p>Note that the return will be false any time a mutation has not
     * occurred.</p>
     * <p/>
     * <blockquote>
     * <p>
     * The actual value sent may either be
     * Unix time (number of seconds since January 1, 1970, as a 32-bit
     * value), or a number of seconds starting from current time. In the
     * latter case, this number of seconds may not exceed 60*60*24*30 (number
     * of seconds in 30 days); if the number sent by a client is larger than
     * that, the server will consider it to be real Unix time value rather
     * than an offset from current time.
     * </p>
     * </blockquote>
     *
     * @param key the key under which this object should be added.
     * @param exp the expiration of this object
     * @param o   the object to store
     * @return a future representing the processing of this operation
     * @throws IllegalStateException in the rare circumstance where queue
     *                               is too full to accept any more requests
     */
    public Future<Boolean> add(String key, int exp, Object o) {
        return client.add(key, exp, o);
    }

    /**
     * Set an object in the cache regardless of any existing value.
     * <p/>
     * <p>
     * The <code>exp</code> value is passed along to memcached exactly as
     * given, and will be processed per the memcached protocol specification:
     * </p>
     * <p/>
     * <p>Note that the return will be false any time a mutation has not
     * occurred.</p>
     * <p/>
     * <blockquote>
     * <p>
     * The actual value sent may either be
     * Unix time (number of seconds since January 1, 1970, as a 32-bit
     * value), or a number of seconds starting from current time. In the
     * latter case, this number of seconds may not exceed 60*60*24*30 (number
     * of seconds in 30 days); if the number sent by a client is larger than
     * that, the server will consider it to be real Unix time value rather
     * than an offset from current time.
     * </p>
     * </blockquote>
     *
     * @param <T>
     * @param key the key under which this object should be added.
     * @param exp the expiration of this object
     * @param o   the object to store
     * @param tc  the transcoder to serialize and unserialize the value
     * @return a future representing the processing of this operation
     * @throws IllegalStateException in the rare circumstance where queue
     *                               is too full to accept any more requests
     */
    public <T> Future<Boolean> set(String key, int exp, T o, Transcoder<T> tc) {
        return client.set(key, exp, o, tc);
    }

    /**
     * Set an object in the cache (using the default transcoder)
     * regardless of any existing value.
     * <p/>
     * <p>
     * The <code>exp</code> value is passed along to memcached exactly as
     * given, and will be processed per the memcached protocol specification:
     * </p>
     * <p/>
     * <p>Note that the return will be false any time a mutation has not
     * occurred.</p>
     * <p/>
     * <blockquote>
     * <p>
     * The actual value sent may either be
     * Unix time (number of seconds since January 1, 1970, as a 32-bit
     * value), or a number of seconds starting from current time. In the
     * latter case, this number of seconds may not exceed 60*60*24*30 (number
     * of seconds in 30 days); if the number sent by a client is larger than
     * that, the server will consider it to be real Unix time value rather
     * than an offset from current time.
     * </p>
     * </blockquote>
     *
     * @param key the key under which this object should be added.
     * @param exp the expiration of this object
     * @param o   the object to store
     * @return a future representing the processing of this operation
     * @throws IllegalStateException in the rare circumstance where queue
     *                               is too full to accept any more requests
     */
    public Future<Boolean> set(String key, int exp, Object o) {
        return client.set(key, exp, o);
    }

    public <T> Future<Boolean> replace(String key, int exp, T o, Transcoder<T> tc) {
        return client.replace(key, exp, o, tc);
    }

    public Future<Boolean> replace(String key, int exp, Object o) {
        return client.replace(key, exp, o);
    }

    public Future<Boolean> append(long cas, String key, Object val) {
        return client.append(cas, key, val);
    }

    public <T> Future<Boolean> append(long cas, String key, T val, Transcoder<T> tc) {
        return client.append(cas, key, val, tc);
    }

    public <T> T get(String key, Transcoder<T> tc) {
        return client.get(key, tc);
    }

    public Object get(String key) {
        return client.get(key);
    }

    public Future<Boolean> delete(String key) {
        return client.delete(key);
    }

    public long incr(String key, int by) {
        return client.incr(key, by);
    }

    public long decr(String key, int by) {
        return client.decr(key, by);
    }

    public long incr(String key, int by, long def, int exp) {
        return client.incr(key, by, def, exp);
    }

    public long decr(String key, int by, long def, int exp) {
        return client.decr(key, by, def, exp);
    }

    public long incr(String key, int by, long def) {
        return client.incr(key, by, def);
    }

    public long decr(String key, int by, long def) {
        return client.decr(key, by, def);
    }

    private void init() throws Exception {
        if(StringUtils.isNotEmpty(userName)){
            AuthDescriptor ad = new AuthDescriptor(new String[]{"PLAIN"}, new PlainCallbackHandler(userName, pwd));
            client = new MemcachedClient(
                    new ConnectionFactoryBuilder().setProtocol(ConnectionFactoryBuilder.Protocol.BINARY).setAuthDescriptor(ad).build(),
                    AddrUtil.getAddresses(cacheServerAddress));
            if(client!=null){
                log.info("init ali memcached success!");
            }else{
                log.error("init ali memcached failed!");
            }
        }else{
            client = new MemcachedClient(AddrUtil.getAddresses(cacheServerAddress));
        }

    }

    public void afterPropertiesSet() {
        address = cacheServerAddress != null && cacheServerAddress.size() > 0;
        if (address) {
            try {
                init();
            } catch (Exception e) {
                log.error("init memcached client error!", e);
            }
        } else {
            log.error("no memcached address!");
        }
    }

    public void shutdown() {
        try {
            client.shutdown();
        } catch (Exception e) {
            log.debug("Memcached shutdown error!", e);
        }
    }
}
